import { AxiosError } from 'axios'
import { ref, onMounted, onUnmounted, toValue } from 'vue'

type TCallback = (...args: any[]) => void

/**
 * 封装请求
 * @param api 请求api
 * @param params 初始请求参数
 * @param immediate 是否立即执行
 * @param isPage 是否分页
 * @param dealResData 处理返回数据
 */
export default function useFetch<T>(
  api: (...args: any[]) => Promise<any>,
  params?: any,
  immediate = false,
  isPage = false,
  dealResData?: (data: T) => T,
) {
  const loading = ref(false)
  const res = ref<T>()
  const successSet = ref(new Set())
  const errorSet = ref(new Set())

  const changeLoading = (newLoading: boolean) => {
    loading.value = newLoading
  }

  function isObj<T>(obj: T): boolean {
    return (
      typeof obj === 'object' &&
      obj !== null &&
      Object.prototype.toString.call(obj) === '[object Object]'
    )
  }

  // 处理请求参数
  function dealParams(params?: any, query?: any) {
    if (!params && !query) {
      return
    }

    if (params) {
      const paramsVal = toValue(params)
      if (query) {
        const queryVal = toValue(query)
        if (isObj(paramsVal) && isObj(queryVal)) {
          return {
            ...paramsVal,
            ...queryVal,
          }
        }
        return queryVal
      }
      return paramsVal
    }

    if (query) {
      return toValue(query)
    }
  }

  /*
   * @param query 请求参数
   * @param isFile 是否导出文件
   * @param other 其他参数，回调函数中需要
   **/
  async function execute<T, U>(query?: T, isFile = false, other?: U) {
    try {
      changeLoading(true)
      const postData = dealParams(params, query)
      const result = await api(postData)

      let status, message, lastData
      if (isFile) {
        status = result.status
        lastData = result.data
        message = result.statusText
      } else {
        status = result.data.status
        lastData = isPage ? result?.data?.data?.data : result?.data?.data
        message = result.data.message
      }

      if (status === 200) {
        const dealData = dealResData ? dealResData(lastData) : lastData
        res.value = dealData
        if (successSet.value.size) {
          successSet.value.forEach((callback: TCallback) => {
            callback?.(dealData, other)
          })
        }
      } else {
        errorSet.value.forEach((callback: TCallback) => {
          callback?.(new AxiosError(message))
        })
      }
    } catch (e) {
      errorSet.value.forEach((callback: TCallback) => {
        callback?.(e)
      })
    } finally {
      changeLoading(false)
    }
  }

  onMounted(() => {
    if (immediate) {
      execute()
    }
  })

  onUnmounted(() => {
    successSet.value.clear()
    errorSet.value.clear()
    res.value = undefined
  })

  // 成功
  function onFetchSuccess(callback?: TCallback) {
    if (callback) successSet.value.add(callback)
  }

  // 失败
  function onFetchError(callback?: TCallback) {
    if (callback) errorSet.value.add(callback)
  }

  return {
    res,
    loading,
    execute,
    onFetchSuccess,
    onFetchError,
  }
}
